<?php
/**
 * RSS文件输出
 * 
 * $Id: rss.php 121 2010-05-28 07:57:29Z Administrator $
 * $Revision: 121 $
 * $Author: Administrator $
 * $Date: 2010-05-28 15:57:29 +0800 (星期五, 28 五月 2010) $
 * 生成合法的RSS文件。要求服务器支持DOM,本功能要求服务
 * 可以将生成的RSS直接保存到服务器。
 * 
 * @author glzaboy<glzaboy@163.com>
 * @package fastlib
 * @subpackage classes
 * @copyright 2005-2010 (c) glzaboy
 */
if (! defined ( '__FASTLIB__' )) {
	if (__DEBUG__) {
		die ( sprintf ( 'Forbidden 403 at file %s on line %d', __FILE__, __LINE__ ) );
	} else {
		die ( 'Forbidden 403' );
	}
}
/**
 * 生成RSS文件类
 * 
 * 可以将生成的RSS直接保存到服务器。
 * 支持外挂XSL样式文件。要求服务器为php5
 * 
 * @author glzaboy <glzaboy@163.com>
 */
class rss
{
	/**
	 * DOMDocument对像
	 *
	 * @var object
	 */
	private $xmldoc = null;
	/**
	 * RSS的channel结点
	 *
	 * @var object
	 */
	private $channel = null;
	/**
	 * 标记数据验证库
	 *
	 * @var Array
	 */
	private $tagXml = array ();
	/**
	 * 生成文档编码格式
	 *
	 * @var String
	 */
	private $charset = null;
	/**
	 * 生成RSS的构造涵数
	 *
	 * @param String $charset 编码
	 * @return rss
	 */
	public function __construct($charset = 'utf8') {
		if (PHP_VERSION <= '5') {
			return null;
		}
		$this->charset = $charset;
		$this->xmldoc = new DOMDocument ( '1.0', $this->charset );
		$rss = $this->addNode ( array ('rss' => null, 'attributes' => array ('version' => '2.0' ) ), null );
		$this->channel = $this->addNode ( array ('channel' => null ), $rss );
		$this->rssInit ();
		return true;
	}
    /**
     * 初始化RSS标记库
     *
     */
    private function rssInit ()
    {
        $this->tagXml=array(
            'channel'=>array(
                /**
                 * 频道的名称
                 */
                'title'=>true,
                /**
                 * 频道的网站的URL链接
                 */
                'link'=>true,
                /**
                 * 频道描述
                 */
                'description'=>true,
                /*可选的选项*/
                /**
                 * 语言
                 */
                'language'=>true,
                /**
                 * 版权声明
                 */
                'copyright'=>true,
                /**
                 * 责任编辑邮件
                 */
                'managingEditor'=>true,
                /**
                 * 技术支持邮件
                 */
                'webMaster'=>true,
                /**
                 * 发布日期
                 */
                'pubDate'=>true,
                /**
                 * 最后修改时间
                 */
                'lastBuildDate'=>true,
                /**
                 * 频道所属的一个或多个分类
                 */
                'category'=>true,
                /**
                 * 生成频道的程序名称的字符串
                 */
                'generator'=>true,
                /**
                 * 指向该RSS文件所用格式说明文档的URL链接地址
                 */
                'docs'=>true,
                /**
                 * 注册一个cloud来处理获得频道的更新通知
                 * 
                 * <cloud domain="rpc.sys.com" port="80" path="/RPC2" 
                 * registerProcedure="myCloud.rssPleaseNotify" 
                 * protocol="xml-rpc" />
                 * 你需要发送一个XML-RPC消息到rpc.sys.com的80端口，路径为/RPC2。
                 * 调用的程序为为myCloud.rssPleaseNotify。 
                 */
                'cloud'=>true,//
                /**
                 * 生存时间
                 */
                'ttl'=>true,
                /**
                 * 频道PICS 内容分级信息
                 */
                'rating'=>true,
                /**
                 * 频道中显示的文本输入区
                 */
                'textInput'=>array(
                    /**
                     * 提交按钮标签
                     */
                    'title'=>true,
                    /**
                     * 描述
                     */
                    'description'=>true,
                    /**
                     * 文本对象名称
                     */
                    'name'=>true,
                    /**
                     * 请求的CGI脚本的URL链接地址
                     */
                    'link'=>true
                ),
                /**
                 * 提示聚合器,可以跳过那些小时的时间段
                 */
                'skipHours'=>true,
                /**
                 * 提示聚合器，可以跳过那些天的时间段
                 */
                'skipDays'=>true,
                /**
                 * 标题图片
                 */
                'image'=>array(
                    /**
                     * 是图像文件的URL链接地址
                     */
                    'url'=>true,
                    /**
                     * 描述图像，当频道在HTML中显示时，用于HTML语言中<img>alt属性
                     */
                    'title'=>true,
                    /**
                     * 是要连接站点url当显示频道时图像连接指向该站点
                     */
                    'link'=>true,
                    /**
                     * 就是link的TITLE属性中文本，它将在调用网页时显示出来
                     */
                    'description'=>true,
                    /**
                     * 宽,最大144，默认88
                     */
                    'width'=>true,
                    /**
                     * 高最大400，默认31
                     */
                    'height'=>true
                )
            ),
            'item'=>array(
                /**
                 * 
                 */
                'title'=>true,//item标题
                /**
                 * 
                 */
                'description'=>true,//item摘要
                /*可选*/
                /**
                 * URL链接地址
                 */
                'link'=>true,
                /**
                 * 作者的电子邮件地址
                 */
                'author'=>true,
                /**
                 * 包含item在一个或多个分类中
                 */
                'category'=>true,
                /**
                 * item相关的评论的URL链接地址
                 * <comments>http://ekzemplo.com/entry/4403/comments</comments>
                 */
                'comments'=>true,
                /**
                 * 附加的媒体对象
                 * <enclosure url="http://site.com/site.mp3" length="12" type="audio/mpeg" />
                 */
                'enclosure'=>true,
                /**
                 * 唯一确定item身份的字符串
                 */
                'guid'=>true,
                /**
                 * rss频道来源<source url="http://site.org/links2.xml">site</source>
                 */
                'source'=>true,
                /**
                 * 发布时间
                 */
                'pubDate'=>true
            )
        );
    }
	/**
	 * 过滤非法的标记
	 *
	 * @param mixed $nodeArr 标记库属性
	 * @param Array $tagXml 选取的标记库
	 */
	private function filter(&$nodeArr, $tagXml) {
		if (is_array ( $nodeArr )) {
			foreach ( $nodeArr as $k => $v ) {
				if (! $tagXml [$k]) {
					unset ( $nodeArr [$k] );
				}
				if (is_array ( $v )) {
					$this->filter ( $nodeArr [$k], $tagXml [$k] );
				}
			}
		}
	}
	/**
	 * 增加一个item或channel
	 *
	 * @param Array $nodeArr 结点属性
	 * @param String $options 增加选项默认为item,否则增加到channel中
	 */
	public function addItem(&$nodeArr, $options = 'item') {
		switch ($options) {
			case 'item' :
				$this->filter ( $nodeArr, $this->tagXml ['item'] );
				$item = $this->addNode ( array ('item' => null ), $this->channel );
				foreach ( $nodeArr as $k => $v ) {
					if ($k == 'pubDate') {
						$v = date ( 'D, d M Y H:i:s O', $this->makeTimeStamp ( $v ) );
					}
					if (! is_array ( $v )) {
						$v = charTounicode ( $v );
					}
					$this->addNode ( array ($k => $v ), $item );
				}
				break;
			default :
				$this->filter ( $nodeArr, $this->tagXml ['channel'] );
				foreach ( $nodeArr as $k => $v ) {
					if ($k == 'pubDate') {
						$v = date ( 'D, d M Y H:i:s O', $this->makeTimeStamp ( $v ) );
					}
					if (! is_array ( $v )) {
						$v = charTounicode ( $v );
					}
					$this->addNode ( array ($k => $v ), $this->channel );
				}
		}
		$nodeArr = null;
		unset ( $nodeArr );
	}
	/**
	 * 根据字符串返回时间戳
	 *
	 * @param String $string 字串
	 * @return Int 生成的时间戳
	 */
	private function makeTimeStamp($string) {
		if (empty ( $string )) {
			// use "now":
			$time = time ();
		} elseif (preg_match ( '/^\d{14}$/', $string )) {
			// it is mysql timestamp format of YYYYMMDDHHMMSS?            
			$time = mktime ( substr ( $string, 8, 2 ), substr ( $string, 10, 2 ), substr ( $string, 12, 2 ), substr ( $string, 4, 2 ), substr ( $string, 6, 2 ), substr ( $string, 0, 4 ) );
		} elseif (is_numeric ( $string )) {
			// it is a numeric string, we handle it as timestamp
			$time = ( int ) $string;
		} else {
			// strtotime should handle it
			$time = strtotime ( $string );
			if ($time == - 1 || $time === false) {
				// strtotime() was not able to parse $string, use "now":
				$time = time ();
			}
		}
		return $time;
	}
	/**
	 * 增加一个结点
	 *
	 * @param Array $nodeArr 结点属性
	 * @param XMLDOC $nodeParent 增加到那个结点下面
	 * @return XMLDOC 增加的结点
	 */
	private function addNode($nodeArr, $nodeParent) {
		$nodeInfo = array ();
		foreach ( $nodeArr as $k => $v ) {
			if (strtolower ( $k ) == 'attributes') {
				$nodeInfo ['attributes'] = $v;
				continue;
			} else {
				$nodeInfo ['tag'] = $k;
				$nodeInfo ['nodeValue'] = $v;
			}
		}
		$node = $this->node ( $nodeInfo ['tag'], $nodeInfo ['nodeValue'], $nodeInfo ['attributes'] ? $nodeInfo ['attributes'] : array () );
		if (is_array ( $nodeInfo ['nodeValue'] )) {
			$this->addNode ( $nodeInfo ['nodeValue'], $node );
		}
		if (! $nodeParent) {
			$nodeParent = $this->xmldoc;
		}
		$nodeParent->appendChild ( $node );
		return $node;
	}
	/**
	 * 生成叶结点XML对象
	 *
	 * @param String $nodeName XML标签名
	 * @param STRING $textValue XML标签的值
	 * @param Array $attributes XML标签的属性
	 * @return unknown
	 */
	private function node($nodeName, $textValue = null, $attributes = array()) {
		$params = array ();
		array_push ( $params, $nodeName );
		if (is_string ( $textValue )) {
			array_push ( $params, $textValue );
		}
		$node = call_user_method_array ( 'createElement', $this->xmldoc, $params );
		if (is_array ( $attributes )) {
			foreach ( $attributes as $k => $v ) {
				$node->setAttribute ( $k, $v );
			}
		}
		return $node;
	}
	/**
	 * 得到RSS的XML格式
	 *
	 * @param String $xsl XSL文件名称
	 * @return String XML格式的RSS
	 */
	private function getXML($xsl = null) {
		if (! $xsl) {
			return $this->xmldoc->saveXML ();
		} else {
			$xml = $this->xmldoc->saveXML ();
			$xml = preg_replace ( '/(\<\?xml[^>]+)>/', "$1>" . '<?xml-stylesheet type="text/xsl" title="XSL Formatting" href="' . $xsl . '"?>', $xml );
			return $xml;
		}
	}
	/**
	 * 输出XML
	 *
	 * @param String $file 文件名，为空时直接输出到浏览器
	 * @param String $xsl 附加的XSL，要求是可以WEB访问的
	 * @return bool true
	 */
	public function outrss($file = null, $xsl = null) {
		if ($file == null) {
			header ( "Content-type: application/xml" );
			echo $this->getXML ( $xsl );
		} else {
			file_put_contents ( $file, $this->getXML ( $xsl ) );
		}
		return true;
	}
	/**
	 * 返回本模块信息
	 *
	 * @return string
	 */
	public function printInfo() {
		$version = '$Revision: 121 $';
		$moudle = __CLASS__;
		$return = '';
		$return .= startTable ();
		$return .= printRow ( array (array ('class' => 'h', 'content' => '<center>' . $moudle . '</center>', 'colspan' => '2' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'author' ), array ('class' => 'v', 'content' => 'glzaboy' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'version' ), array ('class' => 'v', 'content' => $version ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'Atom support' ), array ('class' => 'v', 'content' => 'true' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'require php' ), array ('class' => 'v', 'content' => 'php5 or later.' ) ) );
		$return .= endTable ();
		return $return;
	}
}